/*************************************************
* Title：TinyToolKit 
* Author：Opportunity 
* Describe：AB包管理器
 * 读取AB包配置表
 * 加载和卸载AB包资源
* CreateTime：2020-04-04 16:56:48
* Version：1.0
* Modify Recorder：
*************************************************/

using System;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using Object = UnityEngine.Object;

namespace TinyToolKit.AssetLoad
{
    public class ABLoadHelper
    {
        //存储已经加载的AB包资源
        //Key为AB包名的Crc值
        private Dictionary<uint, AssetBundleItem> _AssetBundleItemDic = new Dictionary<uint, AssetBundleItem>();
        public Dictionary<uint, AssetBundleItem> _abItemDic => _AssetBundleItemDic;

        //所有的AB包缓存资源池，可以根据资源路径的Crc值来找到对应资源
        //key为Path的Crc值
        private Dictionary<uint, ResourceItem> _ResourceItemDic = new Dictionary<uint, ResourceItem>();
        
        private UnityWebRequest request;
        private AssetBundleConfig _config;

        private List<string> abNames = new List<string>();

        #region Public Func

        /// <summary>
        /// 设置AB包配置表
        /// </summary>
        /// <param name="content">配置表内容</param>
        /// <param name="dataFormat">配置表的数据格式</param>
        /// <returns>返回配置表</returns>
        public AssetBundleConfig SetABConfig(DataFormat dataFormat,string content)
        {
            if (content.IsNullOrEmpty()) return null;
            
            AssetBundleConfig config = DeSerializeABConfig(dataFormat,content);
            ParseABConfig(config);
            return config;
        }
        
        /// <summary>
        /// 加载本地的AB包配置表
        /// </summary> 
        /// <param name="filePath">配置表的本地路径</param>
        /// <param name="dataFormat">配置表的数据存储类型</param>
        public AssetBundleConfig LoadABConfig(DataFormat dataFormat,string filePath)
        {
            string content = TinyUtility.File.LoadTextFile(filePath);
            if (content.IsNullOrEmpty()) return null;
            
            AssetBundleConfig config = DeSerializeABConfig(dataFormat,content);
            if (config == null) return null;

            ParseABConfig(config);
            return config;
        }

        /// <summary>
        /// 异步加载AB包配置表
        /// </summary>
        /// <param name="url">AB包配置表的地址</param>
        /// <param name="callback">回调函数</param>
        public void LoadABConfigAsync(string url, DataFormat dataFormat, Action<AssetBundleConfig> callback = null)
        {
            LoadABConfigAsync(url, dataFormat, "", callback);
        }

        /// <summary>
        /// 异步加载AB包配置表
        /// </summary>
        /// <param name="url">AB包配置表的地址</param>
        /// <param name="saveDirPath">配置表保存的本地文件夹路径</param>
        /// <param name="callback">回调函数</param>
        public void LoadABConfigAsync(string url,DataFormat dataFormat, string saveDirPath, Action<AssetBundleConfig> callback = null)
        {
            if (_config != null && _ResourceItemDic.Count > 0)
            {
                callback?.Invoke(_config);
                return;
            }

            TinyUtility.File.LoadTextFileAsync(url, content =>
            {
                if (saveDirPath.IsNullOrEmpty() == false)
                    TinyUtility.File.SaveTextFile(saveDirPath, content);
                
                AssetBundleConfig config = DeSerializeABConfig(dataFormat,content);
                ParseABConfig(config);
                callback?.Invoke(config);
            });
        }
        
        /// <summary>
        /// 根据资源路径的Crc值来加载中间类ResourceItem
        /// 如果已经预加载资源了，则直接返回ResourceItem，否则先加载本地的AB包资源
        /// </summary>
        /// <param name="crc">资源路径的Crc值</param>
        /// <param name="abDirPath">本地AB包在磁盘上的文件夹路径。</param>
        /// <returns>返回该资源的ResourceItem</returns>
        public ResourceItem LoadResItem(uint crc,string abDirPath)
        {
            //先判断AB包资源缓存池内是否已经加载了该资源  
            if (_ResourceItemDic.TryGetValue(crc, out var item) == false || item == null)
            {
                TLogTool.Error("在AB包配置表中未查找到路径Crc值对应的资源:" + crc);
                return null;
            }

            if (item.assetbundle != null)
            {
                TLogTool.Info($"【加载缓存资源对象】加载{item.abName}完成");
                return item;
            }

            //如果AB资源池之前未加载该资源，则现在开始加载
            //加载该AB包所依赖的其他AB资源
            if (item.abDependencies != null)
            {
                foreach (var abName in item.abDependencies)
                {
                    LoadAssetBundle(abDirPath, abName,item.encrypt);
                }
            }
          
            AssetBundle ab = LoadAssetBundle(abDirPath, item.abName,item.encrypt);
            if (ab != null)
                item.assetbundle = ab;
            else
                TLogTool.Error("【AB包版本更新】加载AB包资源为Null");

            return item;
        }

        /// <summary>
        /// 按顺序预加载资源列表里所有资源对应的AB包及其依赖项
        /// </summary>
        /// <param name="assetFullPaths">需要加载的资源列表</param>
        /// <param name="uriPrefix">AB包资源统一的下载地址</param>
        /// <param name="saveDirPath">AB资源保存路径，如果保存路径为空，则加载到缓存里</param>
        /// <param name="callBack">预加载完成时的回调方法</param>
        public void LoadABAsync(List<string> assetFullPaths, string uriPrefix, string saveDirPath = "",
            Action callBack = null)
        {
            if (assetFullPaths == null || assetFullPaths.Count <= 0)
            {
                callBack?.Invoke();
                return;
            }

            string tempFullPath = assetFullPaths[0];
            LoadAssetBundleAsync(tempFullPath, uriPrefix, saveDirPath, () =>
            {
                assetFullPaths.RemoveAt(0);
                LoadABAsync(assetFullPaths, uriPrefix, saveDirPath, callBack);
            });
        }

        /// <summary>
        /// 如果异步加载的UnityWebRequest不为空，则返回当前下载的进度
        /// </summary>
        /// <returns>下载的进度</returns>
        public float GetDownloadProgress()
        {
            if (request != null && request.isDone == false)
                return request.downloadProgress;
            return 0;
        }

        /// <summary>
        /// 根据ResourceItem释放对应的AB资源
        /// </summary>
        /// <param name="item">需要释放的ResourceItem</param>
        public void ReleaseAsset(ResourceItem item)
        {
            if (item == null) return;
            if (item.abDependencies != null && item.abDependencies.Count > 0)
            {
                for (int i = 0; i < item.abDependencies.Count; i++)
                {
                    UnloadAssetBundle(item.abDependencies[i]);
                }
            }

            UnloadAssetBundle(item.abName);
        }

        /// <summary>
        /// 根据资源路径来查找ResourceItem
        /// </summary>
        public ResourceItem GetResourceItem(string path)
        {
            uint crc = CRC32.GetCRC32(path);
            if (_ResourceItemDic.TryGetValue(crc, out var item) == false)
            {
                TLogTool.Error("从AB包资源缓存池中查找失败：", path);
                return null;
            }

            return item;
        }

        /// <summary>
        /// 根据资源名称，返回资源路径
        /// </summary>
        public string GetAssetPath(string assetName)
        {
            if (_config == null)
            {
                TLogTool.Error("请预先加载AB包资源配置表！");
                return "";
            }

            for (int i = 0; i < _config.ABList.Count; i++)
            {
                if (_config.ABList[i].assetName == assetName)
                {
                    return _config.ABList[i].path;
                }
            }

            return "";
        }

        #endregion

        #region Internal Func
        
        //反序列化AB包配置表
        private AssetBundleConfig DeSerializeABConfig(DataFormat dataFormat,string content)
        {
            AssetBundleConfig config = null;
            switch (dataFormat)
            {
                case DataFormat.Json:
                    config = TinyUtility.Json.ToObject<AssetBundleConfig>(content); 
                    break;
                case DataFormat.Encrypt:
                    EncryptDecryptBase encryptDecrypt = new EncryptDecrypt_Caesar();
                    string msg = encryptDecrypt.Decrypt(content);
                    config = TinyUtility.Json.ToObject<AssetBundleConfig>(msg); 
                    break;
            }

            return config;
        }

        //解析config
        private void ParseABConfig(AssetBundleConfig config)
        {
            if (config == null)
            {
                TLogTool.Error("Config不能为Null，解析AB包配置表失败");
                return;
            }
            
            for (int i = 0; i < config.ABList.Count; i++)
            {
                ABBase abBase = config.ABList[i];

                if (_ResourceItemDic.ContainsKey(abBase.crc) == false)
                {
                    ResourceItem item = new ResourceItem
                    {
                        crc = abBase.crc,
                        assetName = abBase.assetName,
                        abName = abBase.abName,
                        encrypt = abBase.encrypt,
                        abDependencies = abBase.abDependencies
                    };
                    _ResourceItemDic.Add(item.crc, item);
                }
            }

            _config = config;
        }

        /// <summary>
        /// 加载AB包资源
        /// 先判断缓存池中是否已经存储该资源，如果已经存在，则直接返回
        /// 如果缓存池中没有该资源，则从本地同步加载，并保存到缓存池中
        /// </summary>
        /// <param name="abDirPath">AB包资源在磁盘上所在的文件夹路径</param>
        /// <param name="abName">AB包资源的名字</param>
        /// <param name="isEncrypt">是否进行偏差值的处理</param>
        /// <returns>AB包资源</returns>
        private AssetBundle LoadAssetBundle(string abDirPath, string abName,bool isEncrypt)
        {
            abDirPath = TinyUtility.Path.GetRegularPath(abDirPath);
            uint crc = CRC32.GetCRC32(abName);

            if (_AssetBundleItemDic.TryGetValue(crc, out var item) == false)
            {
                //从本地加载AB包资源
                AssetBundle ab = null;
                string abPath = Path.Combine(abDirPath, abName);
                if (File.Exists(abPath))
                {
                    ab = isEncrypt ? AssetBundle.LoadFromFile(abPath, 0, 18) : AssetBundle.LoadFromFile(abPath);
                }

                if (ab == null)
                {
                    TLogTool.Error($"【本地加载AB包】加载本地AB包{abName}失败\n加载路径:" + abPath);
                    return null;
                }

                item = TReferencePool.Acquire<AssetBundleItem>(); //由于频繁创建，所以创建引用池
                item.assetBundle = ab;
                item.refCount++;
                _AssetBundleItemDic.Add(crc, item);

                TLogTool.Info($"【本地加载AB包】加载本地AB包{abName}完成\n加载路径：" + abPath);
            }
            else
            {
                item.refCount++;
                TLogTool.Info($"【加载缓存AB包】从缓存池中加载AB包{abName}完成\n加载路径：" + Path.Combine(abDirPath, abName));
            }

            return item.assetBundle;
        }

        //从AB包缓存池中卸载AB包资源
        private void UnloadAssetBundle(string abName)
        {
            uint crc = CRC32.GetCRC32(abName);
            if (_AssetBundleItemDic.TryGetValue(crc, out var item) && item != null)
            {
                item.refCount--;
                //如果还有被引用，或者AB包已经被卸载，则直接返回
                if (item.refCount > 0 || item.assetBundle == null) return;
                //Unload(true) 可能会导致克隆出来的预制体对象丢失贴图和材质
                //在Instantiate将运存中的预制体克隆到内存后，根据依赖关系找到对应的贴图和材质。
                //所以当预制体的AB包卸载后，丢失引用，但克隆的对象还在。
                item.assetBundle.Unload(true);
                _AssetBundleItemDic.Remove(crc);
                TReferencePool.Release(item);

                TLogTool.Info($"【AB包资源卸载】从AB包缓存池中卸载AB包{abName}完成");
            }
            else
            {
                TLogTool.Info($"【AB包资源卸载】AB包缓存池中不存在AB包{abName}");
            }
        }

        /// <summary>
        /// 预加载资源所在的AB包及其依赖项资源
        /// </summary>
        /// <param name="assetFullPath">需要加载资源的路径</param>
        /// <param name="uriPrefix">AB包资源的统一下载地址</param>
        /// <param name="callBack">AB资源加载完成后执行的回调</param>  
        private void LoadAssetBundleAsync(string assetFullPath, string uriPrefix, Action callBack = null)
        {
            LoadAssetBundleAsync(assetFullPath, uriPrefix, "", callBack);
        }

        /// <summary>
        /// 预加载资源所在的AB包及其依赖项资源
        /// </summary>
        /// <param name="assetFullPath">需要加载资源的路径</param>
        /// <param name="uriPrefix">AB包资源的统一下载地址</param>
        /// <param name="saveDirPath">AB资源保存路径</param>
        /// <param name="callBack">AB资源加载完成后执行的回调</param>  
        private void LoadAssetBundleAsync(string assetFullPath, string uriPrefix, string saveDirPath, Action callBack = null)
        {
            if (assetFullPath.IsNullOrEmpty())
            {
                TLogTool.Error("需要加载的资源路径不能为空");
                return;
            }

            //先尝试从缓存池中加载
            uint crc = CRC32.GetCRC32(assetFullPath);
            if (_ResourceItemDic.TryGetValue(crc, out var resItem) == false || resItem == null)
            {
                TLogTool.Error("在AssetBundleConfig中不存在该资源:" + assetFullPath);
                return;
            }

            if (resItem.assetbundle != null)
            {
                callBack?.Invoke();
                TLogTool.Info($"【加载缓存AB包资源】从缓存池中加载{resItem.abName}完成");

                return;
            }

            //添加该资源的依赖AB包名
            abNames.Clear();
            if (resItem.abDependencies != null)
            {
                foreach (var abName in resItem.abDependencies)
                {
                    abNames.Add(abName);
                }
            }

            abNames.Add(resItem.abName);
            string[] names = abNames.ToArray();

            if (saveDirPath.IsNullOrEmpty() == false)
            {
                DownLoadABAsync(uriPrefix, names, saveDirPath, (name, isSuccess) =>
                {
                    if (isSuccess)
                        TLogTool.Info($"【下载服务器的AB包资源】异步下载服务器AB包资源{name}到本地完成\n下载路径：{uriPrefix}/{name}");
                    else
                        TLogTool.Error("异步加载的AB包资源为空：" + name);

                    abNames.Remove(name);
                    if (abNames.Count == 0)
                        callBack?.Invoke();
                });
            }
            else
            {
                LoadABCacheAsync(uriPrefix, names, (name, abItem) =>
                {
                    if (abItem != null)
                    {
                        TLogTool.Info($"【加载服务器的AB包资源】异步加载服务器AB包资源{name}到缓存完成\n加载路径：{uriPrefix}/{name}");
                        resItem.assetbundle = abItem.assetBundle;
                        abItem.refCount++;
                    }
                    else
                    {
                        TLogTool.Error("异步加载的AB包资源为空：" + name);
                    }
                    
                    abNames.Remove(name);
                    if (abNames.Count == 0)
                        callBack?.Invoke();
                });
            }
        }

        /// <summary>
        /// 异步加载abName数组中所有的AssetBundle资源
        /// </summary>
        /// <param name="uriPrefix">AB包资源的统一下载地址</param>
        /// <param name="abNames">需要下载的AB名数组</param>
        /// <param name="saveDirPath">AB资源保存路径</param>
        /// <param name="callback">资源下载后的回调方法</param>
        public void DownLoadABAsync(string uriPrefix, string[] abNames, string saveDirPath,
            Action<string, bool> callback = null)
        {
            //获取abName数组中还未缓存资源的AB包名
            List<string> tempABName = new List<string>();
            for (int i = 0; i < abNames.Length; i++)
            {
                uint crc = CRC32.GetCRC32(abNames[i]);
                if (_AssetBundleItemDic.TryGetValue(crc, out var item) == false || item == null)
                {
                    tempABName.Add(abNames[i]);
                }
                else //如果AB资源已经缓存，则直接获取
                {
                    item.refCount++;
                    callback?.Invoke(abNames[i], true);
                }
            }

            if (tempABName.Count <= 0) return;

            TinyTool.Instance.StartCoroutine(DownLoadABAsync(uriPrefix, tempABName.ToArray(), (abName, data) =>
            {
                bool isSuccess = false;
                uint crc = CRC32.GetCRC32(abName);
                if (_AssetBundleItemDic.TryGetValue(crc, out var item) == false)
                {
                    //LoadFromMemory 不建议使用，容易导致内存翻倍
                    //AssetBundle ab = AssetBundle.LoadFromMemory(data);

                    string savePath = Path.Combine(saveDirPath, abName);
                    isSuccess = TinyUtility.File.SaveByteFile(savePath, data);
                }
                else
                {
                    item.refCount++;
                }

                callback?.Invoke(abName, isSuccess);
            }));
        }

        /// <summary>
        /// 异步加载所有的AB资源到缓存中
        /// </summary>
        /// <param name="uriPrefix">AB包资源的统一下载地址</param>
        /// <param name="abNames">要下载的AB名数组</param>
        /// <param name="callback">所有AB资源加载后的回调方法</param>
        public void LoadABCacheAsync(string uriPrefix, string[] abNames, Action callback = null)
        {
            int count = abNames.Length;
            LoadABCacheAsync(uriPrefix, abNames, (name, abItem) =>
            {
                count--;
                if (count <= 0) callback?.Invoke();
            });
        }

        /// <summary>
        /// 异步加载abNames数组中所有的AssetBundle资源
        /// </summary>
        /// <param name="uriPrefix">AB包资源的统一下载地址</param>
        /// <param name="abNames">需要下载的AB名数组</param>
        /// <param name="callback">资源下载后的回调方法</param>
        private void LoadABCacheAsync(string uriPrefix, string[] abNames, Action<string, AssetBundleItem> callback = null)
        {
            //获取abName数组中还未缓存资源的AB包名 
            List<string> tempABName = new List<string>();
            for (int i = 0; i < abNames.Length; i++)
            {
                uint crc = CRC32.GetCRC32(abNames[i]);
                if (_AssetBundleItemDic.TryGetValue(crc, out var item) == false || item == null)
                {
                    tempABName.Add(abNames[i]);
                }
                else //如果AB资源已经缓存，则直接获取
                {
                    callback?.Invoke(abNames[i], item);
                }
            }

            if (tempABName.Count <= 0) return;

            TinyTool.Instance.StartCoroutine(DownLoadABAsync(uriPrefix, tempABName.ToArray(), (abName, ab) =>
            {
                uint crc = CRC32.GetCRC32(abName);
                if (_AssetBundleItemDic.TryGetValue(crc, out var item) == false)
                {
                    if (ab == null)
                    {
                        TLogTool.Error("加载AB资源失败：" + abName);
                        return;
                    }

                    item = TReferencePool.Acquire<AssetBundleItem>();
                    item.assetBundle = ab;

                    if (_AssetBundleItemDic.ContainsKey(crc) == false)
                        _AssetBundleItemDic.Add(crc, item);
                }
                
                callback?.Invoke(abName, item);
            }));
        }

        //非缓存加载模式
        private IEnumerator DownLoadABAsync(string uriPrefix, string[] abNames, Action<string, byte[]> callback)
        {
            for (int i = 0; i < abNames.Length; i++)
            {
                string uriStr = uriPrefix + "/" + abNames[i];
                using (request = UnityWebRequest.Get(uriStr))
                {
                    yield return request.SendWebRequest();

                    if (request.isNetworkError || request.isHttpError)
                    {
                        TLogTool.Error("AB资源异步加载错误：" + request.error);
                    }
                    else
                    {
                        var data = request.downloadHandler.data;
                        request = null;
                        if (data != null && data.Length > 0)
                            callback?.Invoke(abNames[i], data);
                        else
                            TLogTool.Error("加载的数据为空：" + abNames);
                    }
                }
            }
        }

        //缓存加载模式 
        private IEnumerator DownLoadABAsync(string uriPrefix, string[] abNames, Action<string, AssetBundle> callback)
        {
            for (int i = 0; i < abNames.Length; i++)
            {
                string uriStr = uriPrefix + "/" + abNames[i];

                request = UnityWebRequestAssetBundle.GetAssetBundle(uriStr);
                yield return request.SendWebRequest();
                if (request.isNetworkError || request.isHttpError)
                {
                    TLogTool.Error($"AB资源（路径：{uriStr}）异步加载错误:{request.error}");
                }
                else
                {
                    AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
                    if (ab != null)
                        callback?.Invoke(abNames[i], ab);
                    else
                        TLogTool.Error("加载AB资源失败：" + abNames[i]);
                }
            }
        }

        #endregion
    }

    public class AssetBundleItem : IReference
    {
        public AssetBundle assetBundle;
        public int refCount;

        public void Clear()
        {
            assetBundle = null;
            refCount = 0;
        }
    }

    public class ResourceItem
    {
        public uint crc; //资源路径的Crc
        public string assetName = string.Empty; //资源的文件名
        public string abName = string.Empty; //资源的AB包名
        public bool encrypt;//是否加密
        public List<string> abDependencies; //资源所依赖的AB包名

        public AssetBundle assetbundle; //该资源加载完成的AB包
        public Object obj = null; //资源对象
        public int GUID = 0;
        public float lastUsedTime = 0.0f; //资源最后使用的时间
        public int refCount = 0; //引用计数
    }
}